<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport"
          content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover">
    <meta name="format-detection" content="telephone=no">
    <style type="text/css">

#watermark {

  position: relative;
  overflow: hidden;
}

#watermark .x {
  position: absolute;
  top: 800;
  left: 400;
  color: #3300ff;
  font-size: 50px;
  pointer-events: none;
  opacity:0.3;
  filter:Alpha(opacity=50);
  
  
}
    </style>


    <style type="text/css">
 html{color:#333;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;text-rendering:optimizelegibility;font-family:Helvetica Neue,PingFang SC,Verdana,Microsoft Yahei,Hiragino Sans GB,Microsoft Sans Serif,WenQuanYi Micro Hei,sans-serif}html.borderbox *,html.borderbox :after,html.borderbox :before{box-sizing:border-box}article,aside,blockquote,body,button,code,dd,details,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hr,input,legend,li,menu,nav,ol,p,pre,section,td,textarea,th,ul{margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,menu,nav,section{display:block}audio,canvas,video{display:inline-block}body,button,input,select,textarea{font:300 1em/1.8 PingFang SC,Lantinghei SC,Microsoft Yahei,Hiragino Sans GB,Microsoft Sans Serif,WenQuanYi Micro Hei,Helvetica,sans-serif}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}blockquote{position:relative;color:#999;font-weight:400;border-left:1px solid #1abc9c;padding-left:1em;margin:1em 3em 1em 2em}@media only screen and (max-width:640px){blockquote{margin:1em 0}}abbr,acronym{border-bottom:1px dotted;font-variant:normal}abbr{cursor:help}del{text-decoration:line-through}address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:400}ol,ul{list-style:none}caption,th{text-align:left}q:after,q:before{content:""}sub,sup{font-size:75%;line-height:0;position:relative}:root sub,:root sup{vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}a{color:#1abc9c}a:hover{text-decoration:underline}.typo a{border-bottom:1px solid #1abc9c}.typo a:hover{border-bottom-color:#555;color:#555}.typo a:hover,a,ins{text-decoration:none}.typo-u,u{text-decoration:underline}mark{background:#fffdd1;border-bottom:1px solid #ffedce;padding:2px;margin:0 5px}code,pre,pre tt{font-family:Courier,Courier New,monospace}pre{background:hsla(0,0%,97%,.7);border:1px solid #ddd;padding:1em 1.5em;display:block;-webkit-overflow-scrolling:touch}hr{border:none;border-bottom:1px solid #cfcfcf;margin-bottom:.8em;height:10px}.typo-small,figcaption,small{font-size:.9em;color:#888}b,strong{font-weight:700;color:#000}[draggable]{cursor:move}.clearfix:after,.clearfix:before{content:"";display:table}.clearfix:after{clear:both}.clearfix{zoom:1}.textwrap,.textwrap td,.textwrap th{word-wrap:break-word;word-break:break-all}.textwrap-table{table-layout:fixed}.serif{font-family:Palatino,Optima,Georgia,serif}.typo-dl,.typo-form,.typo-hr,.typo-ol,.typo-p,.typo-pre,.typo-table,.typo-ul,.typo dl,.typo form,.typo hr,.typo ol,.typo p,.typo pre,.typo table,.typo ul,blockquote{margin-bottom:1rem}h1,h2,h3,h4,h5,h6{font-family:PingFang SC,Helvetica Neue,Verdana,Microsoft Yahei,Hiragino Sans GB,Microsoft Sans Serif,WenQuanYi Micro Hei,sans-serif;color:#000;line-height:1.35}.typo-h1,.typo-h2,.typo-h3,.typo-h4,.typo-h5,.typo-h6,.typo h1,.typo h2,.typo h3,.typo h4,.typo h5,.typo h6{margin-top:1.2em;margin-bottom:.6em;line-height:1.35}.typo-h1,.typo h1{font-size:2em}.typo-h2,.typo h2{font-size:1.8em}.typo-h3,.typo h3{font-size:1.6em}.typo-h4,.typo h4{font-size:1.4em}.typo-h5,.typo-h6,.typo h5,.typo h6{font-size:1.2em}.typo-ul,.typo ul{margin-left:1.3em;list-style:disc}.typo-ol,.typo ol{list-style:decimal;margin-left:1.9em}.typo-ol ol,.typo-ol ul,.typo-ul ol,.typo-ul ul,.typo li ol,.typo li ul{margin-bottom:.8em;margin-left:2em}.typo-ol ul,.typo-ul ul,.typo li ul{list-style:circle}.typo-table td,.typo-table th,.typo table caption,.typo table td,.typo table th{border:1px solid #ddd;padding:.5em 1em;color:#666}.typo-table th,.typo table th{background:#fbfbfb}.typo-table thead th,.typo table thead th{background:hsla(0,0%,95%,.7)}.typo table caption{border-bottom:none}.typo-input,.typo-textarea{-webkit-appearance:none;border-radius:0}.typo-em,.typo em,caption,legend{color:#000;font-weight:inherit}.typo-em{position:relative}.typo-em:after{position:absolute;top:.65em;left:0;width:100%;overflow:hidden;white-space:nowrap;content:"\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB"}.typo img{max-width:100%}.common-content{font-weight:400;color:#353535;line-height:1.75rem;white-space:normal;word-break:normal;font-size:1rem}.common-content img{display:block;max-width:100%;background-color:#eee}.common-content audio,.common-content video{width:100%;background-color:#eee}.common-content center,.common-content font{margin-top:1rem;display:inline-block}.common-content center{width:100%}.common-content pre{margin-top:1rem;padding-left:0;padding-right:0;position:relative;overflow:hidden}.common-content pre code{font-size:.8rem;font-family:Consolas,Liberation Mono,Menlo,monospace,Courier;display:block;width:100%;box-sizing:border-box;padding-left:1rem;padding-right:1rem;overflow-x:auto}.common-content hr{border:none;margin-top:1.5rem;margin-bottom:1.5rem;border-top:1px solid #f5f5f5;height:1px;background:none}.common-content b,.common-content h1,.common-content h2,.common-content h3,.common-content h4,.common-content h5,.common-content strong{font-weight:700}.common-content h1,.common-content h2{font-size:1.125rem;margin-bottom:.45rem}.common-content h3,.common-content h4,.common-content h5{font-size:1rem;margin-bottom:.45rem}.common-content p{font-weight:400;color:#353535;margin-top:.15rem}.common-content .orange{color:#ff5a05}.common-content .reference{font-size:1rem;color:#888}.custom-rich-content h1{margin-top:0;font-weight:400;font-size:15.25px;border-bottom:1px solid #eee;line-height:2.8}.custom-rich-content li,.custom-rich-content p{font-size:14px;color:#888;line-height:1.6}table.hljs-ln{margin-bottom:0;border-spacing:0;border-collapse:collapse}table.hljs-ln,table.hljs-ln tbody,table.hljs-ln td,table.hljs-ln tr{box-sizing:border-box}table.hljs-ln td{padding:0;border:0}table.hljs-ln td.hljs-ln-numbers{min-width:15px;color:rgba(27,31,35,.3);text-align:right;white-space:nowrap;cursor:pointer;user-select:none}table.hljs-ln td.hljs-ln-code,table.hljs-ln td.hljs-ln-numbers{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:12px;line-height:20px;vertical-align:top}table.hljs-ln td.hljs-ln-code{position:relative;padding-right:10px;padding-left:10px;overflow:visible;color:#24292e;word-wrap:normal;white-space:pre}video::-webkit-media-controls{overflow:hidden!important}video::-webkit-media-controls-enclosure{width:calc(100% + 32px);margin-left:auto}.button-cancel{color:#888;border:1px solid #888;border-radius:3px;margin-right:12px}.button-cancel,.button-primary{-ms-flex-positive:1;flex-grow:1;height:35px;display:inline-block;font-size:15px;text-align:center;line-height:36px}.button-primary{color:#fff;background-color:#ff5a05;border-radius:3px}@font-face{font-family:iconfont;src:url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.eot);src:url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.eot#iefix) format("embedded-opentype"),url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.woff) format("woff"),url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.ttf) format("truetype"),url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.svg#iconfont) format("svg")}@font-face{font-family:player-font;src:url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.eot);src:url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.eot#iefix) format("embedded-opentype"),url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.woff) format("woff"),url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.ttf) format("truetype"),url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.svg#player-font) format("svg")}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-webkit-text-stroke-width:.2px;-moz-osx-font-smoothing:grayscale}html{background:#fff;min-height:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{width:100%}body.fixed{overflow:hidden;position:fixed;width:100vw;height:100vh}i{font-style:normal}a{word-wrap:break-word;-webkit-tap-highlight-color:rgba(0,0,0,0)}a:hover{text-decoration:none}.fade-enter-active,.fade-leave-active{transition:opacity .3s}.fade-enter,.fade-leave-to{opacity:0}.MathJax,.MathJax_CHTML,.MathJax_MathContainer,.MathJax_MathML,.MathJax_PHTML,.MathJax_PlainSource,.MathJax_SVG{outline:0}.ios-app-switch .js-audit{display:none}._loading_wrap_{position:fixed;width:100vw;height:100vh;top:50%;left:50%;transform:translate(-50%,-50%);z-index:999}._loading_div_class_,._loading_wrap_{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center}._loading_div_class_{word-wrap:break-word;padding:.5rem .75rem;text-align:center;z-index:9999;font-size:.6rem;max-width:60%;color:#fff;border-radius:.25rem;-ms-flex-direction:column;flex-direction:column}._loading_div_class_ .message{color:#353535;font-size:16px;line-height:3}.spinner{animation:circle-rotator 1.4s linear infinite}.spinner *{line-height:0;box-sizing:border-box}@keyframes circle-rotator{0%{transform:rotate(0deg)}to{transform:rotate(270deg)}}.path{stroke-dasharray:187;stroke-dashoffset:0;transform-origin:center;animation:circle-dash 1.4s ease-in-out infinite,circle-colors 5.6s ease-in-out infinite}@keyframes circle-colors{0%{stroke:#ff5a05}to{stroke:#ff5a05}}@keyframes circle-dash{0%{stroke-dashoffset:187}50%{stroke-dashoffset:46.75;transform:rotate(135deg)}to{stroke-dashoffset:187;transform:rotate(450deg)}}.confirm-box-wrapper,.confirm-box-wrapper .mask{position:absolute;top:0;left:0;right:0;bottom:0}.confirm-box-wrapper .mask{background:rgba(0,0,0,.6)}.confirm-box-wrapper .confirm-box{position:fixed;top:50%;left:50%;width:267px;background:#fff;transform:translate(-50%,-50%);border-radius:7px}.confirm-box-wrapper .confirm-box .head{margin:0 18px;font-size:18px;text-align:center;line-height:65px;border-bottom:1px solid #d9d9d9}.confirm-box-wrapper .confirm-box .body{padding:18px;padding-bottom:0;color:#353535;font-size:12.5px;max-height:150px;overflow:auto}.confirm-box-wrapper .confirm-box .foot{display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;padding:18px}.confirm-box-wrapper .confirm-box .foot .button-cancel{border:1px solid #d9d9d9}.hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8f8}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}




    </style>
    <style type="text/css">
        .button-cancel[data-v-87ffcada]{color:#888;border:1px solid #888;border-radius:3px;margin-right:12px}.button-cancel[data-v-87ffcada],.button-primary[data-v-87ffcada]{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;height:35px;display:inline-block;font-size:15px;text-align:center;line-height:36px}.button-primary[data-v-87ffcada]{color:#fff;background-color:#ff5a05;border-radius:3px}.pd[data-v-87ffcada]{padding-left:1.375rem;padding-right:1.375rem}.article[data-v-87ffcada]{max-width:70rem;margin:0 auto}.article .article-unavailable[data-v-87ffcada]{color:#fa8919;font-size:15px;font-weight:600;line-height:24px;border-radius:5px;padding:12px;background-color:#f6f7fb;margin-top:20px}.article .article-unavailable .iconfont[data-v-87ffcada]{font-size:12px}.article .main[data-v-87ffcada]{padding:1.25rem 0;margin-bottom:52px}.article-title[data-v-87ffcada]{color:#353535;font-weight:400;line-height:1.65rem;font-size:1.34375rem}.article-info[data-v-87ffcada]{color:#888;font-size:.9375rem;margin-top:1.0625rem}.article-content[data-v-87ffcada]{margin-top:1.0625rem}.article-content.android video[data-v-87ffcada]::-webkit-media-controls-fullscreen-button{display:none}.copyright[data-v-87ffcada]{color:#b2b2b2;padding-bottom:20px;margin-top:20px;font-size:13px}.audio-player[data-v-87ffcada]{width:100%;margin:20px 0}.to-comment[data-v-87ffcada]{overflow:hidden;padding-top:10px;margin-bottom:-30px}.to-comment a.button-primary[data-v-87ffcada]{float:right;height:20px;font-size:12px;line-height:20px;padding:4px 8px;cursor:pointer}.article-comments[data-v-87ffcada]{margin-top:2rem}.article-comments h2[data-v-87ffcada]{text-align:center;color:#888;position:relative;z-index:1;margin-bottom:1rem}.article-comments h2[data-v-87ffcada]:before{border-top:1px dotted #888;content:"";position:absolute;top:56%;left:0;width:100%;z-index:-1}.article-comments h2 span[data-v-87ffcada]{font-size:15.25px;font-weight:400;padding:0 1rem;background:#fff;display:inline-block}.article-sub-bottom[data-v-87ffcada]{z-index:10;cursor:pointer}.switch-btns[data-v-87ffcada]{height:76px;cursor:pointer;padding-top:24px;padding-bottom:24px;border-bottom:10px solid #f6f7fb;position:relative}.switch-btns[data-v-87ffcada]:before{content:" ";height:1px;background:#e8e8e8;position:absolute;top:0;left:0;-webkit-box-sizing:border-box;box-sizing:border-box;left:1.375rem;right:1.375rem}.switch-btns .btn[data-v-87ffcada]{height:38px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.switch-btns .btn .tag[data-v-87ffcada]{-webkit-box-flex:0;-ms-flex:0 0 62px;flex:0 0 62px;text-align:center;color:#888;font-size:14px;border-radius:10px;height:22px;line-height:22px;background:#f6f7fb;font-weight:400}.switch-btns .btn .txt[data-v-87ffcada]{margin-left:10px;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;color:#888;font-size:15px;height:22px;line-height:22px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:400}@media (max-width:769px){.article .breadcrumb[data-v-87ffcada]{padding-top:10px;padding-bottom:10px}}





    </style>

    <style type="text/css">
        .comment-item{list-style-position:inside;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;margin-bottom:1rem}.comment-item a{border-bottom:none}.comment-item .avatar{width:2.625rem;height:2.625rem;-ms-flex-negative:0;flex-shrink:0;border-radius:50%}.comment-item .info{margin-left:.5rem;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.comment-item .info .hd{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.comment-item .info .hd .username{color:#888;font-size:15.25px;font-weight:400;line-height:1.2}.comment-item .info .hd .control{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.comment-item .info .hd .control .btn-share{color:#888;font-size:.75rem;margin-right:1rem}.comment-item .info .hd .control .btn-praise{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:15.25px;text-decoration:none}.comment-item .info .hd .control .btn-praise i{color:#888;display:inline-block;font-size:.75rem;margin-right:.3rem;margin-top:-.01rem}.comment-item .info .hd .control .btn-praise i.on,.comment-item .info .hd .control .btn-praise span{color:#ff5a05}.comment-item .info .bd{color:#353535;font-size:15.25px;font-weight:400;white-space:normal;word-break:break-all;line-height:1.6}.comment-item .info .time{color:#888;font-size:9px;line-height:1}.comment-item .info .reply .reply-hd{font-size:15.25px}.comment-item .info .reply .reply-hd span{margin-left:-12px;color:#888;font-weight:400}.comment-item .info .reply .reply-hd i{color:#ff5a05;font-size:15.25px}.comment-item .info .reply .reply-content{color:#353535;font-size:15.25px;font-weight:400;white-space:normal;word-break:break-all}.comment-item .info .reply .reply-time{color:#888;font-size:9px}




    </style>
</head>
<body>
<div id="app">


    <div data-v-87ffcada="" class="article" id="watermark">
        <p class="x">加微信heibaifk，网盘停止更新</p>
        <div data-v-87ffcada="" class="main main-app">
            <h1 data-v-87ffcada="" class="article-title pd">
                15讲答疑文章（一）：日志和索引相关问题
            </h1>
            <div data-v-87ffcada="" class="article-content typo common-content pd"><img data-v-87ffcada=""
                                                                                        src="https://static001.geekbang.org/resource/image/44/ea/443082ccfc58d56e263f544ee56d1aea.jpg">


                <div data-v-87ffcada="" id="article-content" class="">
                    <div class="text">
                        <p>在今天这篇答疑文章更新前，MySQL实战这个专栏已经更新了14篇。在这些文章中，大家在评论区留下了很多高质量的留言。现在，每篇文章的评论区都有热心的同学帮忙总结文章知识点，也有不少同学提出了很多高质量的问题，更有一些同学帮忙解答其他同学提出的问题。</p><p>在浏览这些留言并回复的过程中，我倍受鼓舞，也尽我所知地帮助你解决问题、和你讨论。可以说，你们的留言活跃了整个专栏的氛围、提升了整个专栏的质量，谢谢你们。</p><p>评论区的大多数留言我都直接回复了，对于需要展开说明的问题，我都拿出小本子记了下来。这些被记下来的问题，就是我们今天这篇答疑文章的素材了。</p><p>到目前为止，我已经收集了47个问题，很难通过今天这一篇文章全部展开。所以，我就先从中找了几个联系非常紧密的问题，串了起来，希望可以帮你解决关于日志和索引的一些疑惑。而其他问题，我们就留着后面慢慢展开吧。</p><h1>日志相关问题</h1><p>我在第2篇文章<a href="https://time.geekbang.org/column/article/68633">《日志系统：一条SQL更新语句是如何执行的？》</a>中，和你讲到binlog（归档日志）和redo log（重做日志）配合崩溃恢复的时候，用的是反证法，说明了如果没有两阶段提交，会导致MySQL出现主备数据不一致等问题。</p><p>在这篇文章下面，很多同学在问，在两阶段提交的不同瞬间，MySQL如果发生异常重启，是怎么保证数据完整性的？</p><!-- [[[read_end]]] --><p>现在，我们就从这个问题开始吧。</p><p>我再放一次两阶段提交的图，方便你学习下面的内容。</p><p><img src="https://static001.geekbang.org/resource/image/ee/2a/ee9af616e05e4b853eba27048351f62a.jpg" alt=""></p><center><span class="reference">图1 两阶段提交示意图</span></center><p>这里，我要先和你解释一个误会式的问题。有同学在评论区问到，这个图不是一个update语句的执行流程吗，怎么还会调用commit语句？</p><p>他产生这个疑问的原因，是把<strong>两个“commit”的概念</strong>混淆了：</p><ul>
<li>他说的“commit语句”，是指MySQL语法中，用于提交一个事务的命令。一般跟begin/start transaction 配对使用。</li>
<li>而我们图中用到的这个“commit步骤”，指的是事务提交过程中的一个小步骤，也是最后一步。当这个步骤执行完成后，这个事务就提交完成了。</li>
<li>“commit语句”执行的时候，会包含“commit 步骤”。</li>
</ul><p>而我们这个例子里面，没有显式地开启事务，因此这个update语句自己就是一个事务，在执行完成后提交事务时，就会用到这个“commit步骤“。</p><p>接下来，我们就一起分析一下<strong>在两阶段提交的不同时刻，MySQL异常重启会出现什么现象。</strong></p><p>如果在图中时刻A的地方，也就是写入redo log 处于prepare阶段之后、写binlog之前，发生了崩溃（crash），由于此时binlog还没写，redo log也还没提交，所以崩溃恢复的时候，这个事务会回滚。这时候，binlog还没写，所以也不会传到备库。到这里，大家都可以理解。</p><p>大家出现问题的地方，主要集中在时刻B，也就是binlog写完，redo log还没commit前发生crash，那崩溃恢复的时候MySQL会怎么处理？</p><p>我们先来看一下崩溃恢复时的判断规则。</p><ol>
<li>
<p>如果redo log里面的事务是完整的，也就是已经有了commit标识，则直接提交；</p>
</li>
<li>
<p>如果redo log里面的事务只有完整的prepare，则判断对应的事务binlog是否存在并完整：<br>
a.  如果是，则提交事务；<br>
b.  否则，回滚事务。</p>
</li>
</ol><p>这里，时刻B发生crash对应的就是2(a)的情况，崩溃恢复过程中事务会被提交。</p><p>现在，我们继续延展一下这个问题。</p><h2>追问1：MySQL怎么知道binlog是完整的?</h2><p>回答：一个事务的binlog是有完整格式的：</p><ul>
<li>statement格式的binlog，最后会有COMMIT；</li>
<li>row格式的binlog，最后会有一个XID event。</li>
</ul><p>另外，在MySQL 5.6.2版本以后，还引入了binlog-checksum参数，用来验证binlog内容的正确性。对于binlog日志由于磁盘原因，可能会在日志中间出错的情况，MySQL可以通过校验checksum的结果来发现。所以，MySQL还是有办法验证事务binlog的完整性的。</p><h2>追问2：redo log 和 binlog是怎么关联起来的?</h2><p>回答：它们有一个共同的数据字段，叫XID。崩溃恢复的时候，会按顺序扫描redo log：</p><ul>
<li>如果碰到既有prepare、又有commit的redo log，就直接提交；</li>
<li>如果碰到只有parepare、而没有commit的redo log，就拿着XID去binlog找对应的事务。</li>
</ul><h2>追问3：处于prepare阶段的redo log加上完整binlog，重启就能恢复，MySQL为什么要这么设计?</h2><p>回答：其实，这个问题还是跟我们在反证法中说到的数据与备份的一致性有关。在时刻B，也就是binlog写完以后MySQL发生崩溃，这时候binlog已经写入了，之后就会被从库（或者用这个binlog恢复出来的库）使用。</p><p>所以，在主库上也要提交这个事务。采用这个策略，主库和备库的数据就保证了一致性。</p><h2>追问4：如果这样的话，为什么还要两阶段提交呢？干脆先redo log写完，再写binlog。崩溃恢复的时候，必须得两个日志都完整才可以。是不是一样的逻辑？</h2><p>回答：其实，两阶段提交是经典的分布式系统问题，并不是MySQL独有的。</p><p>如果必须要举一个场景，来说明这么做的必要性的话，那就是事务的持久性问题。</p><p>对于InnoDB引擎来说，如果redo log提交完成了，事务就不能回滚（如果这还允许回滚，就可能覆盖掉别的事务的更新）。而如果redo log直接提交，然后binlog写入的时候失败，InnoDB又回滚不了，数据和binlog日志又不一致了。</p><p>两阶段提交就是为了给所有人一个机会，当每个人都说“我ok”的时候，再一起提交。</p><h2>追问5：不引入两个日志，也就没有两阶段提交的必要了。只用binlog来支持崩溃恢复，又能支持归档，不就可以了？</h2><p>回答：这位同学的意思是，只保留binlog，然后可以把提交流程改成这样：… -&gt; “数据更新到内存” -&gt; “写 binlog” -&gt; “提交事务”，是不是也可以提供崩溃恢复的能力？</p><p>答案是不可以。</p><p>如果说<strong>历史原因</strong>的话，那就是InnoDB并不是MySQL的原生存储引擎。MySQL的原生引擎是MyISAM，设计之初就有没有支持崩溃恢复。</p><p>InnoDB在作为MySQL的插件加入MySQL引擎家族之前，就已经是一个提供了崩溃恢复和事务支持的引擎了。</p><p>InnoDB接入了MySQL后，发现既然binlog没有崩溃恢复的能力，那就用InnoDB原有的redo log好了。</p><p>而如果说<strong>实现上的原因</strong>的话，就有很多了。就按照问题中说的，只用binlog来实现崩溃恢复的流程，我画了一张示意图，这里就没有redo log了。</p><p><img src="https://static001.geekbang.org/resource/image/eb/63/eb838b87e9c20fa00aca50ef154f2a63.jpg" alt=""></p><center><span class="reference">图2 只用binlog支持崩溃恢复</span></center><p>这样的流程下，binlog还是不能支持崩溃恢复的。我说一个不支持的点吧：binlog没有能力恢复“数据页”。</p><p>如果在图中标的位置，也就是binlog2写完了，但是整个事务还没有commit的时候，MySQL发生了crash。</p><p>重启后，引擎内部事务2会回滚，然后应用binlog2可以补回来；但是对于事务1来说，系统已经认为提交完成了，不会再应用一次binlog1。</p><p>但是，InnoDB引擎使用的是WAL技术，执行事务的时候，写完内存和日志，事务就算完成了。如果之后崩溃，要依赖于日志来恢复数据页。</p><p>也就是说在图中这个位置发生崩溃的话，事务1也是可能丢失了的，而且是数据页级的丢失。此时，binlog里面并没有记录数据页的更新细节，是补不回来的。</p><p>你如果要说，那我优化一下binlog的内容，让它来记录数据页的更改可以吗？但，这其实就是又做了一个redo log出来。</p><p>所以，至少现在的binlog能力，还不能支持崩溃恢复。</p><h2>追问6：那能不能反过来，只用redo log，不要binlog？</h2><p>回答：如果只从崩溃恢复的角度来讲是可以的。你可以把binlog关掉，这样就没有两阶段提交了，但系统依然是crash-safe的。</p><p>但是，如果你了解一下业界各个公司的使用场景的话，就会发现在正式的生产库上，binlog都是开着的。因为binlog有着redo log无法替代的功能。</p><p>一个是归档。redo log是循环写，写到末尾是要回到开头继续写的。这样历史日志没法保留，redo log也就起不到归档的作用。</p><p>一个就是MySQL系统依赖于binlog。binlog作为MySQL一开始就有的功能，被用在了很多地方。其中，MySQL系统高可用的基础，就是binlog复制。</p><p>还有很多公司有异构系统（比如一些数据分析系统），这些系统就靠消费MySQL的binlog来更新自己的数据。关掉binlog的话，这些下游系统就没法输入了。</p><p>总之，由于现在包括MySQL高可用在内的很多系统机制都依赖于binlog，所以“鸠占鹊巢”redo log还做不到。你看，发展生态是多么重要。</p><h2>追问7：redo log一般设置多大？</h2><p>回答：redo log太小的话，会导致很快就被写满，然后不得不强行刷redo log，这样WAL机制的能力就发挥不出来了。</p><p>所以，如果是现在常见的几个TB的磁盘的话，就不要太小气了，直接将redo log设置为4个文件、每个文件1GB吧。</p><h2>追问8：正常运行中的实例，数据写入后的最终落盘，是从redo log更新过来的还是从buffer pool更新过来的呢？</h2><p>回答：这个问题其实问得非常好。这里涉及到了，“redo log里面到底是什么”的问题。</p><p>实际上，redo log并没有记录数据页的完整数据，所以它并没有能力自己去更新磁盘数据页，也就不存在“数据最终落盘，是由redo log更新过去”的情况。</p><ol>
<li>
<p>如果是正常运行的实例的话，数据页被修改以后，跟磁盘的数据页不一致，称为脏页。最终数据落盘，就是把内存中的数据页写盘。这个过程，甚至与redo log毫无关系。</p>
</li>
<li>
<p>在崩溃恢复场景中，InnoDB如果判断到一个数据页可能在崩溃恢复的时候丢失了更新，就会将它读到内存，然后让redo log更新内存内容。更新完成后，内存页变成脏页，就回到了第一种情况的状态。</p>
</li>
</ol><h2>追问9：redo log buffer是什么？是先修改内存，还是先写redo log文件？</h2><p>回答：这两个问题可以一起回答。</p><p>在一个事务的更新过程中，日志是要写多次的。比如下面这个事务：</p><pre><code>begin;
insert into t1 ...
insert into t2 ...
commit;
</code></pre><p>这个事务要往两个表中插入记录，插入数据的过程中，生成的日志都得先保存起来，但又不能在还没commit的时候就直接写到redo log文件里。</p><p>所以，redo log buffer就是一块内存，用来先存redo日志的。也就是说，在执行第一个insert的时候，数据的内存被修改了，redo log buffer也写入了日志。</p><p>但是，真正把日志写到redo log文件（文件名是 ib_logfile+数字），是在执行commit语句的时候做的。</p><p>（这里说的是事务执行过程中不会“主动去刷盘”，以减少不必要的IO消耗。但是可能会出现“被动写入磁盘”，比如内存不够、其他事务提交等情况。这个问题我们会在后面第22篇文章《MySQL有哪些“饮鸩止渴”的提高性能的方法？》中再详细展开）。</p><p>单独执行一个更新语句的时候，InnoDB会自己启动一个事务，在语句执行完成的时候提交。过程跟上面是一样的，只不过是“压缩”到了一个语句里面完成。</p><p>以上这些问题，就是把大家提过的关于redo log和binlog的问题串起来，做的一次集中回答。如果你还有问题，可以在评论区继续留言补充。</p><h1>业务设计问题</h1><p>接下来，我再和你分享@ithunter 同学在第8篇文章<a href="https://time.geekbang.org/column/article/70562">《</a><a href="https://time.geekbang.org/column/article/70562">事务到底是隔离的还是不隔离的？</a><a href="https://time.geekbang.org/column/article/70562">》</a>的评论区提到的跟索引相关的一个问题。我觉得这个问题挺有趣、也挺实用的，其他同学也可能会碰上这样的场景，在这里解答和分享一下。</p><p>问题是这样的（我文字上稍微做了点修改，方便大家理解）：</p><blockquote>
<p>业务上有这样的需求，A、B两个用户，如果互相关注，则成为好友。设计上是有两张表，一个是like表，一个是friend表，like表有user_id、liker_id两个字段，我设置为复合唯一索引即uk_user_id_liker_id。语句执行逻辑是这样的：</p>
</blockquote><blockquote>
<p>以A关注B为例：<br>
第一步，先查询对方有没有关注自己（B有没有关注A）<br>
select * from like where user_id = B and liker_id = A;</p>
</blockquote><blockquote>
<p>如果有，则成为好友<br>
insert into friend;</p>
</blockquote><blockquote>
<p>没有，则只是单向关注关系<br>
insert into like;</p>
</blockquote><blockquote>
<p>但是如果A、B同时关注对方，会出现不会成为好友的情况。因为上面第1步，双方都没关注对方。第1步即使使用了排他锁也不行，因为记录不存在，行锁无法生效。请问这种情况，在MySQL锁层面有没有办法处理？</p>
</blockquote><p>首先，我要先赞一下这样的提问方式。虽然极客时间现在的评论区还不能追加评论，但如果大家能够一次留言就把问题讲清楚的话，其实影响也不大。所以，我希望你在留言提问的时候，也能借鉴这种方式。</p><p>接下来，我把@ithunter 同学说的表模拟出来，方便我们讨论。</p><pre><code>CREATE TABLE `like` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `liker_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_id_liker_id` (`user_id`,`liker_id`)
) ENGINE=InnoDB;

CREATE TABLE `friend` (
  id` int(11) NOT NULL AUTO_INCREMENT,
  `friend_1_id` int(11) NOT NULL,
  `firned_2_id` int(11) NOT NULL,
  UNIQUE KEY `uk_friend` (`friend_1_id`,`firned_2_id`)
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
</code></pre><p>虽然这个题干中，并没有说到friend表的索引结构。但我猜测friend_1_id和friend_2_id也有索引，为便于描述，我给加上唯一索引。</p><p>顺便说明一下，“like”是关键字，我一般不建议使用关键字作为库名、表名、字段名或索引名。</p><p>我把他的疑问翻译一下，在并发场景下，同时有两个人，设置为关注对方，就可能导致无法成功加为朋友关系。</p><p>现在，我用你已经熟悉的时刻顺序表的形式，把这两个事务的执行语句列出来：<br>
<img src="https://static001.geekbang.org/resource/image/c4/ed/c45063baf1ae521bf5d98b6d7c0e0ced.png" alt=""></p><center><span class="reference">图3 并发“喜欢”逻辑操作顺序</span></center><p>由于一开始A和B之间没有关注关系，所以两个事务里面的select语句查出来的结果都是空。</p><p>因此，session 1的逻辑就是“既然B没有关注A，那就只插入一个单向关注关系”。session 2也同样是这个逻辑。</p><p>这个结果对业务来说就是bug了。因为在业务设定里面，这两个逻辑都执行完成以后，是应该在friend表里面插入一行记录的。</p><p>如提问里面说的，“第1步即使使用了排他锁也不行，因为记录不存在，行锁无法生效”。不过，我想到了另外一个方法，来解决这个问题。</p><p>首先，要给“like”表增加一个字段，比如叫作 relation_ship，并设为整型，取值1、2、3。</p><blockquote>
<p>值是1的时候，表示user_id 关注 liker_id;<br>
值是2的时候，表示liker_id 关注 user_id;<br>
值是3的时候，表示互相关注。</p>
</blockquote><p>然后，当 A关注B的时候，逻辑改成如下所示的样子：</p><p>应用代码里面，比较A和B的大小，如果A&lt;B，就执行下面的逻辑</p><pre><code>mysql&gt; begin; /*启动事务*/
insert into `like`(user_id, liker_id, relation_ship) values(A, B, 1) on duplicate key update relation_ship=relation_ship | 1;
select relation_ship from `like` where user_id=A and liker_id=B;
/*代码中判断返回的 relation_ship，
  如果是1，事务结束，执行 commit
  如果是3，则执行下面这两个语句：
  */
insert ignore into friend(friend_1_id, friend_2_id) values(A,B);
commit;
</code></pre><p>如果A&gt;B，则执行下面的逻辑</p><pre><code>mysql&gt; begin; /*启动事务*/
insert into `like`(user_id, liker_id, relation_ship) values(B, A, 2) on duplicate key update relation_ship=relation_ship | 2;
select relation_ship from `like` where user_id=B and liker_id=A;
/*代码中判断返回的 relation_ship，
  如果是2，事务结束，执行 commit
  如果是3，则执行下面这两个语句：
*/
insert ignore into friend(friend_1_id, friend_2_id) values(B,A);
commit;
</code></pre><p>这个设计里，让“like”表里的数据保证user_id &lt; liker_id，这样不论是A关注B，还是B关注A，在操作“like”表的时候，如果反向的关系已经存在，就会出现行锁冲突。</p><p>然后，insert … on duplicate语句，确保了在事务内部，执行了这个SQL语句后，就强行占住了这个行锁，之后的select 判断relation_ship这个逻辑时就确保了是在行锁保护下的读操作。</p><p>操作符 “|” 是按位或，连同最后一句insert语句里的ignore，是为了保证重复调用时的幂等性。</p><p>这样，即使在双方“同时”执行关注操作，最终数据库里的结果，也是like表里面有一条关于A和B的记录，而且relation_ship的值是3， 并且friend表里面也有了A和B的这条记录。</p><p>不知道你会不会吐槽：之前明明还说尽量不要使用唯一索引，结果这个例子一上来我就创建了两个。这里我要再和你说明一下，之前文章我们讨论的，是在“业务开发保证不会插入重复记录”的情况下，着重要解决性能问题的时候，才建议尽量使用普通索引。</p><p>而像这个例子里，按照这个设计，业务根本就是保证“我一定会插入重复数据，数据库一定要要有唯一性约束”，这时就没啥好说的了，唯一索引建起来吧。</p><h1>小结</h1><p>这是专栏的第一篇答疑文章。</p><p>我针对前14篇文章，大家在评论区中的留言，从中摘取了关于日志和索引的相关问题，串成了今天这篇文章。这里我也要再和你说一声，有些我答应在答疑文章中进行扩展的话题，今天这篇文章没来得及扩展，后续我会再找机会为你解答。所以，篇幅所限，评论区见吧。</p><p>最后，虽然这篇是答疑文章，但课后问题还是要有的。</p><p>我们创建了一个简单的表t，并插入一行，然后对这一行做修改。</p><pre><code>mysql&gt; CREATE TABLE `t` (
`id` int(11) NOT NULL primary key auto_increment,
`a` int(11) DEFAULT NULL
) ENGINE=InnoDB;
insert into t values(1,2);
</code></pre><p>这时候，表t里有唯一的一行数据(1,2)。假设，我现在要执行：</p><pre><code>mysql&gt; update t set a=2 where id=1;
</code></pre><p>你会看到这样的结果：</p><p><img src="https://static001.geekbang.org/resource/image/36/70/367b3f299b94353f32f75ea825391170.png" alt=""><br>
结果显示，匹配(rows matched)了一行，修改(Changed)了0行。</p><p>仅从现象上看，MySQL内部在处理这个命令的时候，可以有以下三种选择：</p><ol>
<li>
<p>更新都是先读后写的，MySQL读出数据，发现a的值本来就是2，不更新，直接返回，执行结束；</p>
</li>
<li>
<p>MySQL调用了InnoDB引擎提供的“修改为(1,2)”这个接口，但是引擎发现值与原来相同，不更新，直接返回；</p>
</li>
<li>
<p>InnoDB认真执行了“把这个值修改成(1,2)"这个操作，该加锁的加锁，该更新的更新。</p>
</li>
</ol><p>你觉得实际情况会是以上哪种呢？你可否用构造实验的方式，来证明你的结论？进一步地，可以思考一下，MySQL为什么要选择这种策略呢？</p><p>你可以把你的验证方法和思考写在留言区里，我会在下一篇文章的末尾和你讨论这个问题。感谢你的收听，也欢迎你把这篇文章分享给更多的朋友一起阅读。</p><h1>上期问题时间</h1><p>上期的问题是，用一个计数表记录一个业务表的总行数，在往业务表插入数据的时候，需要给计数值加1。</p><p>逻辑实现上是启动一个事务，执行两个语句：</p><ol>
<li>
<p>insert into 数据表；</p>
</li>
<li>
<p>update 计数表，计数值加1。</p>
</li>
</ol><p>从系统并发能力的角度考虑，怎么安排这两个语句的顺序。</p><p>这里，我直接复制 @阿建 的回答过来供你参考：</p><blockquote>
<p>并发系统性能的角度考虑，应该先插入操作记录，再更新计数表。<br>
知识点在<a href="https://time.geekbang.org/column/article/70215">《行锁功过：怎么减少行锁对性能的影响？》</a><br>
因为更新计数表涉及到行锁的竞争，先插入再更新能最大程度地减少事务之间的锁等待，提升并发度。</p>
</blockquote><p>评论区有同学说，应该把update计数表放后面，因为这个计数表可能保存了多个业务表的计数值。如果把update计数表放到事务的第一个语句，多个业务表同时插入数据的话，等待时间会更长。</p><p>这个答案的结论是对的，但是理解不太正确。即使我们用一个计数表记录多个业务表的行数，也肯定会给表名字段加唯一索引。类似于下面这样的表结构：</p><pre><code>CREATE TABLE `rows_stat` (
  `table_name` varchar(64) NOT NULL,
  `row_count` int(10) unsigned NOT NULL,
  PRIMARY KEY (`table_name`)
) ENGINE=InnoDB;
</code></pre><p>在更新计数表的时候，一定会传入where table_name=$table_name，使用主键索引，更新加行锁只会锁在一行上。</p><p>而在不同业务表插入数据，是更新不同的行，不会有行锁。</p><p>评论区留言点赞板：</p><blockquote>
<p>@北天魔狼、@斜面镜子 Bil 和@Bin 等同学，都给出了正确答案；<br>
@果然如此 同学提了一个好问题，虽然引入事务，避免看到”业务上还没提交的更新”，但是Redis的计数被提前看到了。核心原因还是两个系统，不支持一致性视图；<br>
@ 帆帆帆帆帆帆帆帆 同学的问题提醒了大家，count(id)也是可以走普通索引得到的。</p>
</blockquote><p><img src="https://static001.geekbang.org/resource/image/09/77/09c1073f99cf71d2fb162a716b5fa577.jpg" alt=""></p>
                    </div>
                </div>

            </div>
            <div data-v-87ffcada="" class="article-comments pd"><h2 data-v-87ffcada=""><span
                    data-v-87ffcada="">精选留言</span></h2>
                <ul data-v-87ffcada="">
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/13/f8/70/f3a33a14.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">某、人</span>
                            </div>
                            <div class="bd">孔乙己来到酒馆大喊一声老板来二两酒赊着,酒馆生意太好,老板把孔乙己的欠账记录记到小黑板上并记录了孔乙己点的菜单。孔乙己跟别人吹了会牛,忘了叫的几两酒了。又给老板说,老板把酒改成二两。老板也不确定孔乙己叫没叫酒,就去查菜单,发现孔乙己确实点了酒,但是本来就二两,也就难得麻烦了,又要修改小黑板,又要改菜单。直接就给孔乙己说已经改好了。😄 <br></div>
                            <span class="time">2018-12-17 17:07</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">老板看完板，正要告知孔乙己今日总账是赊账二两酒，<br>小二连忙过来拦住，“老板，刚刚孔乙己刚又赊账了一碟茴香豆。”<br>老板大惊，“差点亏了我一碟豆子！我怎不知？”<br>小二道，“老板你方才看板的之时没拿记账笔，我看记账笔没人使用，按店规自然可用。老板你自己没看”<br><br>老板惊呼，“亏的你小心”。<br><br>暗地想店规确有不妥。<br><br>于是把店规“变账须用记账笔。” 改为<br>“改帐均须动笔。纵为不变之帐，仍需覆写之”<br><br>😄</p>
                                <p class="reply-time">2018-12-17 20:50</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/14/57/96/b65bdf43.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">萤火虫</span>
                            </div>
                            <div class="bd">林老师的每次更新我都会跟着看 跟着学 已经坚持15节课了 受益良多 只是心里有时会反问自己 底层原理有那么重要吗？ 会用不就行了吗？ 自己不知道该怎么推翻这些想法 加上自己有个不好的习惯 就是容易放弃 希望自己能够坚持到最后。  <br></div>
                            <span class="time">2018-12-17 11:37</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">加油。<br><br>说下我自己的理解。<br><br>我在带新人的时候，要求大家在写SQL语句的时候，心里是有数的，知道每个语句执行的结果，以及这些代码会消耗什么资源、如果慢了会慢在哪里、每个语句执行会占用哪些锁等等。<br><br>有的新人会问“为什么需要这么麻烦，我执行一下，看看结果对不对，对了就行，不对就改，是不是也可以？”<br><br>我说不可以。因为如果这样，我们就会受到很多局限，即使我们定位自己是业务开发人员。<br><br>这里我说一个限制：<br><br>这会限制基于数据库的业务架构能力。一个语句可以试，一个五个语句的事务分析就要试很多次，一个复杂业务系统的数据库设计，是试不出来的。<br><br>原理可以帮我们剪枝，排除掉那些理论上明显错误的方案，这样才有精力真的去试那些有限的、可能正确的方案。<br><br><br>我们不需要100%精通MySQL(我自己离这个目标也相去甚远），但是只要多知道一些原理，就能多剪一些枝，架构设计就能少一些错误选项的干扰，设计出来的项目架构正确的可能性更高。<br><br>我自己特别喜欢这个剪枝的过程和感觉，他表示我用以前学习的时间，来节省了现在工作的时间。<br><br><br>当然，“原理”是一个很大的概念，有的原理更接近实战，有的远一些。这个专栏我挑的是跟平时使用相关的原理，以便大家可以有机会边学边用。<br><br>一起加油吧🤝<br><br></p>
                                <p class="reply-time">2018-12-17 12:24</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">Gavin</span>
                            </div>
                            <div class="bd">课后问题：<br>在命令行先执行以下命令（注意不要提交事务）：<br>BEGIN;<br>UPDATE t SET a=2 WHERE id=1;<br><br>新建一个命令行终端，执行以下命令：<br>UPDATE t SET a=2 WHERE id=1;<br><br>从新建的命令行终端的执行结果看，这条更新语句被阻塞了，如果时间足够的话（InnoDB行锁默认等待时间是50秒），还会报锁等待超时的错误。<br>综上，MySQL应该是采用第3种方式处理题述场景。<br><br>对于MySQL为什么采用这种方式，我们可以利用《08 | 事务到底是隔离的还是不隔离的？》图5的更新逻辑图来解释：假设事务C更新后a的值就是2，而事务B执行再执行UPDATE t SET a=2 WHERE id=1;时不按第3种方式处理，即不加锁不更新，那么在事务B中接下来查询a的值将还是1，因为对事务B来说，trx_id为102版本的数据是不可见的，这就违反了“当前读的规则”。<br><br>以上是我的理解与分析，不是很确定准确与否。<br> <br></div>
                            <span class="time">2018-12-17 17:47</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">漂亮</p>
                                <p class="reply-time">2018-12-17 18:08</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">null</span>
                            </div>
                            <div class="bd">看到自己的问题上榜，这是对自己的最大鼓励。<br><br>学习专栏之前，自己只是一个 CRUD boy，平时同事间讨论 MySQL 的问题，自己完全搭不上话，因为对 MySQL 底层原理完全不懂。对 MySQL 的认知就仅限一点：索引能提高查询效率。但是为什么能提高？不知道！！<br><br>现在回想，以前犯过很多错误：<br>1. 主键使用 UUID，非自增主键。<br>2. 滥用索引，其实可以通过“最左前缀原则”来精减索引。<br>3. 不管 SQL 语句是否合理，只要能返回结果集就是好 SQL。<br>4. 建表时字段类型拿捏不准。<br><br>现在都会反复学习专栏的每一篇文章，每次学习都有不一样的收获。<br>第一次可能是：喔，原来有这么个知识点，但对它的实现原理一知半解。<br>第二次却是：对它的实现原理有了更深的认识，加强对知识的理解，基本会形成一个比较清晰的逻辑。<br>第三次是，MySQL 的这种实现原理，是为了解决什么问题等等。<br><br>现在感觉有点“走火入魔”了，以前执行查询语句，关注的多久能返回结果集。<br>现在关注的却是：连接器、分析器、优化器、执行器和 InnoDB 引擎。<br>连接成功后，获取我的权限，查询缓存，命中缓存直接返回，否则进行后续的操作。（记得老师留言区回复过：连接器取权限，执行器用权限。而编写留言到这产生了一个疑问：查询缓存前，应该会校验权限，所以连接器也会用权限？）<br>分析器阶段进行词法分析，解析关键字，字段名，表名等。语法分析判断语法是否正确。（记得第一篇《基础架构》留言提到语义分析，今晚要找资料学习下）。<br>优化器阶段生成执行计划，选择索引（这时会怀疑 MySQL 选择的索引是否最优），能否使用索引下推和覆盖索引减少回表查询，提高性能。<br>执行器阶段调用引擎接口查询数据，Server 层要啥，引擎给啥，InnoDB 只给必要的值。<br>查询结束后，返回结果集，并将结果集放入查询缓存。<br><br>更新语句的关注点是隔离性，视图，MVCC，回滚日志，redo log，binlog，两阶段提交等。<br>写业务代码时，会考虑事务内的 SQL 语句，能否调整 SQL 语句的顺序，减少更新表时行锁对性能的影响。<br>在建表的时，会反复推敲这个索引是否合理。使用普通索引还是唯一索引更为合适。能否通过“最左前缀原则”来减少创建索引的个数。如果索引字段的类型是字符串并长度太长，如何优化使用前缀索引，减少空间占用，提高查询性能。<br><br>学习专栏后，基本上涉及到 MySQL 的内容，这些知识点都会浮现在脑海中。昨天还差点应用这些知识，帮同事优化他的 SQL 语句。昨天跟往常一样，当写代码写累了，就跑到同事那溜达溜达。<br>他正在线上的备库测试查询百万数据要多久，另一位同事建议他使用 force index 强制索引，这次执行 5 秒，再执行零点几秒。<br>他惊乎，为啥这次这么快。我说，这次查了缓存。我还想帮他看看 SQL 语句，是否 MySQL 选择错了索引，导致使用 force index 显式指定索引。说不定使用 order by field 就解决了呢，哈哈哈哈。后面有事，没有继续跟进他这问题了。<br><br>非常感恩，跟着老师学习，让我体会到了学习是一件自然而又充满魅力的事情，也让我从一个基础不牢固的小白，一步步地充实了自己的知识库，另外老师非常尽责，经常半夜回复答疑，希望老师保重身体。谢谢！！ <br></div>
                            <span class="time">2018-12-18 14:11</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">“我说，这次查了缓存”<br><br>哈哈，这个场景好棒，这个画面感，有一种扫地僧的感觉👍🏿<br><br>一起加油<br><br></p>
                                <p class="reply-time">2018-12-18 15:05</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/11/a3/40/e0df3b84.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">力挽狂澜爆炸输出的臭臭宁</span>
                            </div>
                            <div class="bd">针对不能只用binlog完成数据恢复我的理解：<br>按照文中这个话题下的示例，因为MySQL写数据是写在内存里的，不保证落盘，所以commit1的数据也可能丢失；但是恢复只恢复binlog失败的也就是commit2的数据，所以数据会丢失。<br>这样理解对吗？ <br></div>
                            <span class="time">2018-12-20 20:36</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">是的，binlog一来时机控制不好（就是你说的这个），二来内容的能力不足（没有页面信息）<br>👍🏿<br></p>
                                <p class="reply-time">2018-12-20 22:16</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/10/1f/ff/aadcf237.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">Eric</span>
                            </div>
                            <div class="bd">老师，您实在是太良心了。整理这些问题应该很费时间吧。看完答疑之后感觉又加深了一遍印象。像很多知识点都需要反复理解才能真正掌握。答疑来的很及时，感谢！ <br></div>
                            <span class="time">2018-12-17 09:47</span>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/13/fc/e5/c26c67ea.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">陈新仁</span>
                            </div>
                            <div class="bd">【操作符“|”是逻辑或，连同最后一句insert语句里...】<br>老师，“|” 这应该叫位运算符的按位或操作符，逻辑或是“||”吧？<br>这里的幂等性原理就是：A &lt; B: relation_ship = 2 | 1; A &gt; B:relation_ship = 1 | 2；重复插入 3 | 1 或者 3 | 2 。位运算： 2 | 1 == 1 | 2 == 3 | 1 == 3 | 2 == 3。感觉这里想法很巧妙。 <br></div>
                            <span class="time">2018-12-17 11:26</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">你说得对，是按位或，看得很细致👍🏿<br><br>我发个堪误</p>
                                <p class="reply-time">2018-12-17 14:16</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/10/18/22/e817914c.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">于海</span>
                            </div>
                            <div class="bd">在极客时间也学了不少课程，林老师是这其中最认真负责的，好的课程是用“心”写出来的 <br></div>
                            <span class="time">2018-12-17 16:05</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">谢谢🙏 <br>希望大家都有收获</p>
                                <p class="reply-time">2018-12-17 18:14</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/0f/84/49/abb7bfe3.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">观弈道人</span>
                            </div>
                            <div class="bd">萤火虫的问题应该是道出了很多业务开发的心声，工具本来是方便开发人员的却带来了很大学习负担，如springboot 本来是为方便使用spring,现在市场环境（招聘方）要求懂它的原理，导致大量精力投入学习这些上层知识，不断迭代版本不断跟进。 <br></div>
                            <span class="time">2018-12-26 08:04</span>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/14/0c/ca/6173350b.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">郭江伟</span>
                            </div>
                            <div class="bd">创建测试数据：<br>mysql&gt; create table t(id int primary key auto_increment,a int );<br>mysql&gt; insert into t values(1,2);<br>mysql&gt; begin;<br>Query OK, 0 rows affected (0.00 sec)<br>mysql&gt; update t set a=2 where id=1;<br>Query OK, 0 rows affected (0.00 sec)<br>查看系统锁情况：<br>show engine innodb status<br>---TRANSACTION 958998, ACTIVE 51 sec<br>2 lock struct(s), heap size 1136, 1 row lock(s)<br>MySQL thread id 2, OS thread handle 139663691581184, query id 22 localhost root<br>mysql&gt; show processlist;<br>+----+------+-----------+--------------------+---------+------+----------+------------------+<br>| Id | User | Host      | db                 | Command | Time | State    | Info             |<br>+----+------+-----------+--------------------+---------+------+----------+------------------+<br>|  2 | root | localhost | sysbench           | Sleep   |  352 |          | NULL             |<br>|  3 | root | localhost | NULL               | Sleep   |  301 |          | NULL             |<br>+----+------+-----------+--------------------+---------+------+----------+------------------+<br>其中Thread id=2 为update会话，说明系统有锁<br>另一会话执行 update t set a=2 where id=1;<br>ERROR 1205 (HY000): Unknown error 1205 MySQL error code 1205 (ER_LOCK_WAIT_TIMEOUT): Lock wait timeout exceeded; try restarting transaction<br>提交第一个会话查看生成的binlog<br>### INSERT INTO `sysbench`.`t`<br>### SET<br>###   @1=1 &#47;* INT meta=0 nullable=0 is_null=0 *&#47;<br>###   @2=2 &#47;* INT meta=0 nullable=1 is_null=0 *&#47;<br># at 858<br>#181217 14:28:21 server id 9012  end_log_pos 889 CRC32 0xf96f7fcb 	Xid = 20<br>COMMIT&#47;*!*&#47;;<br># at 889<br>#181217 14:42:14 server id 9012  end_log_pos 930 CRC32 0x3de034ba 	Rotate to bin.000089  pos: 4<br>SET @@SESSION.GTID_NEXT= &#39;AUTOMATIC&#39; &#47;* added by mysqlbinlog *&#47; &#47;*!*&#47;;<br>DELIMITER ;<br># End of log file<br>&#47;*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*&#47;;<br>&#47;*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*&#47;;<br>发现没有update的binlog产生，也就是说该语句在server层没有实际执行<br>用hexdump对比update前后的数据行，发现事务id和回滚id也没变，说明innodb没有实际更新行。<br>鉴于该语句产生了行锁，有事务信息，但是没有实际修改，可判断innodb在更新前后值一样时不会实际更新数据 <br></div>
                            <span class="time">2018-12-17 15:13</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">Hexdump前有没有关闭MySQL?</p>
                                <p class="reply-time">2018-12-17 22:10</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/13/b7/00/12149f4e.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">郭刚</span>
                            </div>
                            <div class="bd">结论是方式3：<br>autocommit设置的是0<br><br>实验过程：<br>session1:<br>mysql&gt; update t set a=2 where id=1;<br>Query OK, 0 rows affected (0.00 sec)<br>Rows matched: 1  Changed: 0  Warnings: 0<br>session2:<br>mysql&gt; update t set a=2 where id=1;<br>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction <br></div>
                            <span class="time">2018-12-17 17:54</span>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/13/f8/70/f3a33a14.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">某、人</span>
                            </div>
                            <div class="bd">老师提几个问题：<br>1.事务在执行过程中，binlog是否像redo log一样记录到binlog_cache里？<br>2.为什么把redo log buffer设置成全局参数，binlog cache设置为事务级别参数？<br>3.为什么一般是binlog落盘比redo log更耗时？<br>4.如果sync为1，dump线程是等到binlog 成功flush，再从binlog cache中把binlog event发送给从库？如果非1,是在最后xid写入就从binlog cache中把binlog event发送给从库？ <br></div>
                            <span class="time">2018-12-17 01:22</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">1. 嗯，它有单独的内存，redo log buffer<br><br>2. Binlog cache size也是global 的呀，我还去确认了5.5～5.7，你用的是哪个版本？<br><br>3. 这个数据是怎么得到的😄<br><br>4. 写完磁盘就发，然后再回来flush。 <br>     不是，放在binlog cache表示“这事务还没做完”，不发的</p>
                                <p class="reply-time">2018-12-17 11:55</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/0f/84/49/abb7bfe3.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">观弈道人</span>
                            </div>
                            <div class="bd">丁老师，不好意思，刚才提的A &gt; B, A &lt; B问题，我要再重复一下。A &gt; B: 应该是表示当前业务操作为A用户关注B用户, sql为: insert into like(&#39;a_user_id&#39;, &#39;b_user_id&#39;, 1), 如果反向关联则为insert into like(&#39;b_user_id&#39;, &#39;a_user_id&#39;, 2), 互相关联则relation_ship update 为3, 您的意思是通过relation_ship表示user_id,liker_id哪个为被关注方,哪个是关注方，所以，我还是理解 A &gt; B ,不应该理解成 A 大于 B,而是A 关注 B，我认为A 大于 B的比较是没有意义的，比较疑惑我这样理解偏差在哪里?😖 <br></div>
                            <span class="time">2019-01-02 16:59</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">不是A关注B，就是A大于B，说的是用户id哦</p>
                                <p class="reply-time">2019-01-02 17:26</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/0f/94/59/ac2aa72b.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">Smile</span>
                            </div>
                            <div class="bd">如果 A&gt;B，则执行下面的逻辑<br><br>&#47;* 代码中判断返回的 relation_ship.....<br>如果是 1，事务结束，执行 commit<br>----------<br>老师，这里应该为 “如果是2” 吧<br><br> <br></div>
                            <span class="time">2018-12-18 16:53</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">是的是的，<br>我从上面拷下来后注释忘记改了，<br><br>你看得好细致👍🏿</p>
                                <p class="reply-time">2018-12-18 17:43</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/13/e1/e7/d1b2e914.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">明亮</span>
                            </div>
                            <div class="bd">接上一个提问，如果给redo总体大小设置4G，这时在一个事务中写10G数据是否可成功行呢？ <br></div>
                            <span class="time">2018-12-18 12:55</span>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">mahonebags</span>
                            </div>
                            <div class="bd">我给表新加了一个update_time on update current_timestamp字段，发现会加锁，但是提交后update_time不会变化，而且也没有binlog生成，所以是加锁了但是实际没更新？ <br></div>
                            <span class="time">2018-12-18 10:06</span>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/10/78/31/c7f8d1db.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">Laputa</span>
                            </div>
                            <div class="bd">老师，间隙锁是如何运行的？<br>实际中遇到一个问题：<br>假如我有一张表 table1，id列为主键<br>事务1：<br>begin;<br>select * from table1 where id = 1 for update;<br>此时开始事务2：<br>begin;<br>select * from table1 where id = 1 for update;<br>事务1继续执行：<br>#若id为1的记录不存在则执行：<br>insert into table1 values(1);<br>此时事务1会阻塞,应该是在获取锁；<br>然后事务2也会执行同样的逻辑，因为id为1的记录不存在，也会执行插入操作：<br>insert into table1 values(1);<br>此时事务2会报死锁，事务1会执行成功：<br>Deadlock found when trying to get lock; try restarting transaction；<br><br>业务上的逻辑是这样的：<br>根据ID查找对应的记录，如果不存在就插入对应的记录，若存在就更新对应的记录，当有两个请求同时执行此逻辑且对应ID不存在的时候，就会报错，老师能不解释下间隙锁和如何避免此种情况的死锁，谢谢 <br></div>
                            <span class="time">2018-12-17 23:22</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">这真是个好问题，第21篇会讲到</p>
                                <p class="reply-time">2018-12-19 02:42</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/0f/46/56/5e83f44b.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">Dkey</span>
                            </div>
                            <div class="bd">老师，我想问一下<br>1. redo log跟binlog都是在commit阶段进行sync磁盘操作的吗（双1）<br>2. master线程每秒也会刷盘redo log跟commit阶段刷盘是不冲突的吧。<br>3. sync_binlog为1时发送binlog是在sync到磁盘之后吗？反之是在flush之后。被发送的binlog是从binlog文件读取出来的。<br>感谢回答 <br></div>
                            <span class="time">2018-12-17 13:28</span>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/0f/9f/1d/ec173090.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">melon</span>
                            </div>
                            <div class="bd">思考题：应该是第三种，因为两个事务并行执行该update，有一个会卡住，说明有加锁，而且update语句执行后，查看ibd文件和redo log文件的修改时间都更新了。通过show engine innodb status 进一步验证，查看LSN确实增加了，而且Number of rows updated 也加+1了。 <br></div>
                            <span class="time">2018-12-17 13:24</span>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/13/e5/39/951f89c8.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">信信</span>
                            </div>
                            <div class="bd">如果图1的“写入redo log”是写内存，当时刻B发生crash，重启后这部分redo log都丢失了，那么何谈判断redo log是否有完整的prepare还是commit标志呢？ <br></div>
                            <span class="time">2018-12-17 13:14</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">不是哦，<br>在事务执行期间是在redo log buffer.<br><br>在图中写binlog之前，就已经都写了盘并且fsync了</p>
                                <p class="reply-time">2018-12-17 22:15</p>
                            </div>
                            
                        </div>
                    </li>
                    


                </ul>
            </div>
        </div>
    </div>
</div>
</body>
</html>